import collections
import os
import random
import tkinter as tk
import enum
from tkinter import filedialog, messagebox
from typing import Final
from PIL import ImageTk, Image


class SlideShowOrder(str, enum.Enum):
    RANDOM: Final[str] = "random"
    NAME: Final[str] = "name"
    DATE: Final[str] = "date"


class Application:
    SLIDE_SHOW_INITIAL_DELAY: Final[int] = 5000
    SLIDE_SHOW_CHANGE_DELAY: Final[int] = 1000

    def __init__(self):
        self.window = tk.Tk()
        self.window.title('Slide show')
        self.window.configure(background='black')
        self.window.geometry('200x200')
        self.window_width = self.window.winfo_width()
        self.window_height = self.window.winfo_height()

        self.image_area = tk.Label(self.window)
        self.image_area.configure(background='black')
        self.image_area.pack()
        self.images: list = []
        self.view_history = collections.deque(maxlen=20)
        self.forward_history = list()
        self.order_by: str = SlideShowOrder.RANDOM
        self.current_image = None
        self.current_image_index = None
        self.slide_show_mode = False
        self.slide_show_delay = Application.SLIDE_SHOW_INITIAL_DELAY

        self.context_menu_font = ('', '16')
        self.context_menu = None

        self.window.bind("<Button-3>", lambda event: self.build_context_menu().post(event.x_root, event.y_root))
        self.window.bind("<Key>", self.on_key_pressed)
        self.window.bind("<Configure>", self.on_window_resized)

    def start(self):
        self.window.mainloop()

    def build_context_menu(self):
        menu = tk.Menu(self.window, tearoff=0)
        menu.add_command(label="Open", command=self.choice_directory_and_show_first, font=self.context_menu_font)
        if len(self.images) > 0:
            menu.add_separator()
            if self.slide_show_mode:
                menu.add_command(label="Slide show stop", command=self.stop_slide_show, font=self.context_menu_font)
                menu.add_command(label="Slide show faster", command=self.slide_show_decrement_delay,
                                 font=self.context_menu_font)
                menu.add_command(label="Slide show slower", command=self.slide_show_increment_delay,
                                 font=self.context_menu_font)
            else:
                menu.add_command(label="Slide show start", command=self.start_slide_show, font=self.context_menu_font)
            menu.add_separator()
            menu.add_command(label="Delete", command=self.delete_current_image, font=self.context_menu_font)
            sort_by_menu = tk.Menu(menu, tearoff=0)
            sort_by_menu.add_command(label="Random", command=self.set_order_by_random, font=self.context_menu_font)
            sort_by_menu.add_command(label="Name", command=self.set_order_by_name, font=self.context_menu_font)
            sort_by_menu.add_command(label="Date", command=self.set_order_by_date, font=self.context_menu_font)
            menu.add_cascade(label="Sort by " + self.order_by, menu=sort_by_menu, font=self.context_menu_font)

        menu.add_command(label="Exit", command=self.window.quit, font=self.context_menu_font)
        self.context_menu = menu
        return self.context_menu

    def set_order_by_name(self):
        self.current_image_index = None
        self.slide_show_mode = False
        self.images.sort(key=lambda x: x[0])
        self.order_by = SlideShowOrder.RANDOM

    def set_order_by_random(self):
        self.current_image_index = None
        self.slide_show_mode = False
        self.order_by = SlideShowOrder.NAME

    def set_order_by_date(self):
        self.current_image_index = None
        self.slide_show_mode = False
        self.images.sort(key=lambda x: -x[1])
        self.order_by = SlideShowOrder.DATE

    def choice_directory_and_show_first(self):
        image_directory = tk.filedialog.askdirectory(parent=self.window, initialdir="/", title="Select a directory")
        if image_directory:
            self.images = [(os.path.join(image_directory, f), os.path.getctime(os.path.join(image_directory, f))) for f
                           in os.listdir(image_directory) if
                           os.path.isfile(os.path.join(image_directory, f)) and f.lower().endswith(
                               ('.png', '.jpg', '.jpeg', '.tiff', '.bmp', '.gif'))]
        if len(self.images) > 0:
            self.current_image_index = None
            self.slide_show_mode = False
            self.order_by = SlideShowOrder.RANDOM
            self.show_next_images()
        else:
            tk.messagebox.showerror(title="Ошибка", message="В данной папке нет изображений")

    def delete_current_image(self):
        if self.current_image is not None and tk.messagebox.askyesno(title="Удалить изображение",
                                                                     message="Вы уверены?"):

            current_image_id = next(k for k, v in enumerate(self.images) if v[0] == self.current_image)
            self.images.pop(current_image_id)
            os.remove(self.current_image)
            if self.current_image in self.view_history:
                self.view_history.remove(self.current_image)
            if len(self.view_history) > 0:
                show_image = self.view_history.pop()
                self.show_image(show_image)
            else:
                self.current_image = None
                self.show_next_images()

    def show_next_images(self):
        if len(self.images) > 0:
            if self.current_image is not None:
                self.view_history.append(self.current_image)
            if len(self.forward_history) > 0:
                self.current_image = self.forward_history.pop()
            else:
                if self.order_by == SlideShowOrder.RANDOM:
                    self.current_image = random.choice(self.images)[0]
                else:
                    if self.current_image_index is None or self.current_image_index == len(self.images) - 1:
                        self.current_image_index = 0
                    else:
                        self.current_image_index = self.current_image_index + 1
                    self.current_image = self.images[self.current_image_index][0]
            self.show_image(self.current_image)

    def show_image(self, image_path: str):
        source_image = Image.open(image_path)
        resized_image = self.resize_to_fit(source_image)
        tk_image = ImageTk.PhotoImage(resized_image)
        self.image_area.configure(image=tk_image)
        self.image_area.image = tk_image

    def resize_to_fit(self, image):
        width, height = image.size

        if width > height:
            new_width = self.window_width
            ratio = new_width / width
            new_height = round(height * ratio)
            if new_height > self.window_height:
                new_height = self.window_height
                ratio = new_height / height
                new_width = round(width * ratio)
        else:
            new_height = self.window_height
            ratio = new_height / height
            new_width = round(width * ratio)
            if new_width > self.window_width:
                new_width = self.window_width
                ratio = new_width / width
                new_height = round(height * ratio)
        return image.resize((new_width, new_height))

    def on_key_pressed(self, event):
        if event.keysym == 'Escape':
            self.window.quit()
        elif len(self.images) == 0:
            self.choice_directory_and_show_first()
        elif event.keysym == 'space' or event.keysym == 'Right':
            if self.slide_show_mode:
                self.stop_slide_show()
            else:
                self.show_next_images()
        elif event.keysym == 'BackSpace' or event.keysym == 'Left':
            if len(self.view_history) > 0:
                self.stop_slide_show()
                self.forward_history.append(self.current_image)
                self.current_image = self.view_history.pop()
                self.show_image(self.current_image)
        elif event.keysym == 'Delete':
            self.stop_slide_show()
            self.delete_current_image()
        elif event.keysym == 'Return':
            if self.slide_show_mode:
                self.stop_slide_show()
            else:
                self.start_slide_show()

    def on_window_resized(self, event):
        if event.widget.master is None and (
                self.window_width != self.window.winfo_width() or self.window_height != self.window.winfo_height()):
            self.window_width = self.window.winfo_width()
            self.window_height = self.window.winfo_height()
            if self.current_image is not None:
                self.show_image(self.current_image)

    def start_slide_show(self):
        self.slide_show_mode = True
        if len(self.images) > 0:
            self.show_next_images()
            self.window.after(self.slide_show_delay, self.next_slide_show)

    def stop_slide_show(self):
        self.slide_show_mode = False
        self.slide_show_delay = Application.SLIDE_SHOW_INITIAL_DELAY

    def next_slide_show(self):
        if self.slide_show_mode:
            self.show_next_images()
            self.window.after(self.slide_show_delay, self.next_slide_show)

    def slide_show_increment_delay(self):
        self.slide_show_delay = self.slide_show_delay + Application.SLIDE_SHOW_CHANGE_DELAY

    def slide_show_decrement_delay(self):
        self.slide_show_delay = self.slide_show_delay - Application.SLIDE_SHOW_CHANGE_DELAY


if __name__ == '__main__':
    Application().start()
